/////////////////////////////////////////////////////////////////////////
// $Id: plugin.cc,v 1.13 2004/08/11 11:05:10 vruppert Exp $
/////////////////////////////////////////////////////////////////////////
//
// This file defines the plugin and plugin-device registration functions and
// the device registration functions.  It handles dynamic loading of modules,
// using the LTDL library for cross-platform support.
//
// This file is based on the plugin.c file from plex86, but with significant
// changes to make it work in Bochs.
// Plex86 is Copyright (C) 1999-2000  The plex86 developers team
//
/////////////////////////////////////////////////////////////////////////

#include "bochs.h"
#include "iodev/iodev.h"
#include "plugin.h"

#define LOG_THIS genlog->

#define PLUGIN_INIT_FMT_STRING  "lib%s_LTX_plugin_init"
#define PLUGIN_FINI_FMT_STRING  "lib%s_LTX_plugin_fini"
#define PLUGIN_PATH ""

#ifndef WIN32
#define PLUGIN_FILENAME_FORMAT "libbx_%s.la"
#else
#define PLUGIN_FILENAME_FORMAT "bx_%s.dll"
#endif

extern "C" {

void  (*pluginRegisterIRQ)(unsigned irq, const char* name) = 0;
void  (*pluginUnregisterIRQ)(unsigned irq, const char* name) = 0;

void (*pluginSetHRQ)(unsigned val) = 0;
void (*pluginSetHRQHackCallback)( void (*callback)(void) ) = 0;

int (*pluginRegisterIOReadHandler)(void *thisPtr, ioReadHandler_t callback,
                            unsigned base, const char *name, Bit8u mask) = 0;
int (*pluginRegisterIOWriteHandler)(void *thisPtr, ioWriteHandler_t callback,
                             unsigned base, const char *name, Bit8u mask) = 0;
int (*pluginUnregisterIOReadHandler)(void *thisPtr, ioReadHandler_t callback,
                            unsigned base, Bit8u mask) = 0;
int (*pluginUnregisterIOWriteHandler)(void *thisPtr, ioWriteHandler_t callback,
                             unsigned base, Bit8u mask) = 0;
int (*pluginRegisterIOReadHandlerRange)(void *thisPtr, ioReadHandler_t callback,
                            unsigned base, unsigned end, const char *name, Bit8u mask) = 0;
int (*pluginRegisterIOWriteHandlerRange)(void *thisPtr, ioWriteHandler_t callback,
                             unsigned base, unsigned end, const char *name, Bit8u mask) = 0;
int (*pluginUnregisterIOReadHandlerRange)(void *thisPtr, ioReadHandler_t callback,
                            unsigned begin, unsigned end, Bit8u mask) = 0;
int (*pluginUnregisterIOWriteHandlerRange)(void *thisPtr, ioWriteHandler_t callback,
                             unsigned begin, unsigned end, Bit8u mask) = 0;
int (*pluginRegisterDefaultIOReadHandler)(void *thisPtr, ioReadHandler_t callback,
                            const char *name, Bit8u mask) = 0;
int (*pluginRegisterDefaultIOWriteHandler)(void *thisPtr, ioWriteHandler_t callback,
                             const char *name, Bit8u mask) = 0;
int (*pluginRegisterTimer)(void *this_ptr, void (*funct)(void *),
                            Bit32u useconds, bx_bool continuous, 
bx_bool active, const char* name) = 0;
                            void (*pluginActivateTimer)(unsigned id, Bit32u usec, bx_bool continuous) = 0;

void (*pluginHRQHackCallback)(void);
unsigned pluginHRQ = 0;

plugin_t *plugins = NULL;      /* Head of the linked list of plugins  */
#if BX_PLUGINS
static void plugin_init_one(plugin_t *plugin);
#endif

device_t *devices = NULL;      /* Head of the linked list of registered devices  */

plugin_t *current_plugin_context = NULL;

/************************************************************************/
/* Builtins declarations                                                */
/************************************************************************/

  static void  
builtinRegisterIRQ(unsigned irq, const char* name) 
{
#if 0
  pluginlog->panic("builtinRegisterIRQ called, no pic plugin loaded?");
#else
  bx_devices.register_irq(irq, name);
#endif
}

  static void  
builtinUnregisterIRQ(unsigned irq, const char* name)
{
#if 0
  pluginlog->panic("builtinUnregisterIRQ called, no pic plugin loaded?");
#else
  bx_devices.unregister_irq(irq, name);
#endif
}

  static void
builtinSetHRQ(unsigned val)
{
#if 0
  pluginlog->panic("builtinSetHRQ called, no plugin loaded?");
#else
  pluginHRQ = val;
#endif
}

  static void
builtinSetHRQHackCallback( void (*callback)(void) )
{
#if 0
  pluginlog->panic("builtinSetHRQHackCallback called, no plugin loaded?");
#else
  pluginHRQHackCallback = callback;
#endif
}

  static int
builtinRegisterIOReadHandler(void *thisPtr, ioReadHandler_t callback,
                            unsigned base, const char *name, Bit8u mask)
{
  int ret;
  BX_ASSERT (mask<8);
  ret = bx_devices.register_io_read_handler (thisPtr, callback, base, name, mask);
  pluginlog->ldebug("plugin %s registered I/O read address at %04x", name, base);
  return ret;
}

  static int
builtinRegisterIOWriteHandler(void *thisPtr, ioWriteHandler_t callback,
                             unsigned base, const char *name, Bit8u mask)
{
  int ret;
  BX_ASSERT (mask<8);
  ret = bx_devices.register_io_write_handler (thisPtr, callback, base, name, mask);
  pluginlog->ldebug("plugin %s registered I/O write address at %04x", name, base);
  return ret;
}

  static int
builtinUnregisterIOReadHandler(void *thisPtr, ioReadHandler_t callback,
                            unsigned base, Bit8u mask)
{
  int ret;
  BX_ASSERT (mask<8);
  ret = bx_devices.unregister_io_read_handler (thisPtr, callback, base, mask);
  pluginlog->ldebug("plugin unregistered I/O read address at %04x", base);
  return ret;
}

  static int
builtinUnregisterIOWriteHandler(void *thisPtr, ioWriteHandler_t callback,
                             unsigned base, Bit8u mask)
{
  int ret;
  BX_ASSERT (mask<8);
  ret = bx_devices.unregister_io_write_handler (thisPtr, callback, base, mask);
  pluginlog->ldebug("plugin unregistered I/O write address at %04x", base);
  return ret;
}

  static int
builtinRegisterIOReadHandlerRange(void *thisPtr, ioReadHandler_t callback,
                            unsigned base, unsigned end, const char *name, Bit8u mask)
{
  int ret;
  BX_ASSERT (mask<8);
  ret = bx_devices.register_io_read_handler_range (thisPtr, callback, base, end, name, mask);
  pluginlog->ldebug("plugin %s registered I/O read addresses %04x to %04x", name, base, end);
  return ret;
}

  static int
builtinRegisterIOWriteHandlerRange(void *thisPtr, ioWriteHandler_t callback,
                             unsigned base, unsigned end, const char *name, Bit8u mask)
{
  int ret;
  BX_ASSERT (mask<8);
  ret = bx_devices.register_io_write_handler_range (thisPtr, callback, base, end, name, mask);
  pluginlog->ldebug("plugin %s registered I/O write addresses %04x to %04x", name, base, end);
  return ret;
}

  static int
builtinUnregisterIOReadHandlerRange(void *thisPtr, ioReadHandler_t callback,
                            unsigned begin, unsigned end, Bit8u mask)
{
  int ret;
  BX_ASSERT (mask<8);
  ret = bx_devices.unregister_io_read_handler_range (thisPtr, callback, begin, end, mask);
  pluginlog->ldebug("plugin unregistered I/O read addresses %04x to %04x", begin, end);
  return ret;
}

  static int
builtinUnregisterIOWriteHandlerRange(void *thisPtr, ioWriteHandler_t callback,
                             unsigned begin, unsigned end, Bit8u mask)
{
  int ret;
  BX_ASSERT (mask<8);
  ret = bx_devices.unregister_io_write_handler_range (thisPtr, callback, begin, end, mask);
  pluginlog->ldebug("plugin unregistered I/O write addresses %04x to %04x", begin, end);
  return ret;
}

  static int
builtinRegisterDefaultIOReadHandler(void *thisPtr, ioReadHandler_t callback,
                            const char *name, Bit8u mask)
{
  BX_ASSERT (mask<8);
  bx_devices.register_default_io_read_handler (thisPtr, callback, name, mask);
  pluginlog->ldebug("plugin %s registered default I/O read ", name);
  return 0;
}

  static int
builtinRegisterDefaultIOWriteHandler(void *thisPtr, ioWriteHandler_t callback,
                             const char *name, Bit8u mask)
{
  BX_ASSERT (mask<8);
  bx_devices.register_default_io_write_handler (thisPtr, callback, name, mask);
  pluginlog->ldebug("plugin %s registered default I/O write ", name);
  return 0;
}

  static int
builtinRegisterTimer(void *this_ptr, void (*funct)(void *),
                        Bit32u useconds, bx_bool continuous, 
                        bx_bool active, const char* name)
{
  int id = bx_pc_system.register_timer (this_ptr, funct, useconds, continuous, active, name);
  pluginlog->ldebug("plugin %s registered timer %d", name, id);
  return id;
}

  static void
builtinActivateTimer(unsigned id, Bit32u usec, bx_bool continuous)
{
  bx_pc_system.activate_timer (id, usec, continuous);
  pluginlog->ldebug("plugin activated timer %d", id);
}

#if BX_PLUGINS
/************************************************************************/
/* Plugin initialization / deinitialization                             */
/************************************************************************/

  void
plugin_init_all (void)
{
    plugin_t *plugin;

    pluginlog->info("Initializing plugins");

    for (plugin = plugins; plugin; plugin = plugin->next)
    {
        char *arg_ptr = plugin->args;

        /* process the command line */
        plugin->argc = 0;
        while (plugin->argc < MAX_ARGC)
        {
            while (*arg_ptr && isspace (*arg_ptr))
                arg_ptr++;

            if (!*arg_ptr)
                break;
            plugin->argv[plugin->argc++] = arg_ptr;

            while (*arg_ptr && !isspace (*arg_ptr))
                arg_ptr++;

            if (!*arg_ptr)
                break;
            *arg_ptr++ = '\0';
        }

        /* initialize the plugin */
        if (plugin->plugin_init (plugin, plugin->type, plugin->argc, plugin->argv))
        {
            pluginlog->panic("Plugin initialization failed for %s", plugin->name);
            plugin_abort();
        }

        plugin->initialized = 1;
    }

    return;
}

void
plugin_init_one(plugin_t *plugin)
{
        char *arg_ptr = plugin->args;
 
        /* process the command line */
        plugin->argc = 0;
        while (plugin->argc < MAX_ARGC)
        {
            while (*arg_ptr && isspace (*arg_ptr))
                arg_ptr++;
 
            if (!*arg_ptr)
                break;
            plugin->argv[plugin->argc++] = arg_ptr;
 
            while (*arg_ptr && !isspace (*arg_ptr))
                arg_ptr++;
 
            if (!*arg_ptr)
                break;
            *arg_ptr++ = '\0';
        }
 
        /* initialize the plugin */
        if (plugin->plugin_init (plugin, plugin->type, plugin->argc, plugin->argv))
        {
            pluginlog->info("Plugin initialization failed for %s", plugin->name);
            plugin_abort();
        }
 
        plugin->initialized = 1;
}


  plugin_t *
plugin_unload(plugin_t *plugin)
{
    plugin_t *dead_plug;

    if (plugin->initialized)
        plugin->plugin_fini ();

    lt_dlclose (plugin->handle);
    free (plugin->name);
    free (plugin->args);

    dead_plug = plugin;
    plugin = plugin->next;
    free (dead_plug);

    return plugin;
}


void
plugin_fini_all (void)
{
    plugin_t *plugin;

    for (plugin = plugins; plugin; plugin = plugin_unload (plugin));

    return;
}

  void
plugin_load (char *name, char *args, plugintype_t type)
{
    plugin_t *plugin;

    plugin = (plugin_t *)malloc (sizeof (plugin_t));
    if (!plugin)
    {
      BX_PANIC (("malloc plugin_t failed"));
    }

    plugin->type = type;
    plugin->name = name;
    plugin->args = args;
    plugin->initialized = 0;

    char plugin_filename[BX_PATHNAME_LEN], buf[BX_PATHNAME_LEN];
    sprintf (buf, PLUGIN_FILENAME_FORMAT, name);
    sprintf(plugin_filename, "%s%s", PLUGIN_PATH, buf);

    // Set context so that any devices that the plugin registers will
    // be able to see which plugin created them.  The registration will
    // be called from either dlopen (global constructors) or plugin_init.
    BX_ASSERT (current_plugin_context == NULL);
    current_plugin_context = plugin;
    plugin->handle = lt_dlopen (plugin_filename);
    BX_INFO (("lt_dlhandle is %p", plugin->handle));
    if (!plugin->handle)
    {
      current_plugin_context = NULL;
      BX_PANIC (("dlopen failed for module '%s': %s", name, lt_dlerror ()));
      free (plugin);
      return;
    }

    sprintf (buf, PLUGIN_INIT_FMT_STRING, name);
    plugin->plugin_init =  
      (int  (*)(struct _plugin_t *, enum plugintype_t, int, char *[])) /* monster typecast */
      lt_dlsym (plugin->handle, buf);
    if (plugin->plugin_init == NULL) {
        pluginlog->panic("could not find plugin_init: %s", lt_dlerror ());
        plugin_abort ();
    }

sprintf (buf, PLUGIN_FINI_FMT_STRING, name);
    plugin->plugin_fini = (void (*)(void)) lt_dlsym (plugin->handle, buf);
    if (plugin->plugin_init == NULL) {
        pluginlog->panic("could not find plugin_fini: %s", lt_dlerror ());
        plugin_abort ();
    }
    pluginlog->info("loaded plugin %s",plugin_filename);


    /* Insert plugin at the _end_ of the plugin linked list. */
    plugin->next = NULL;

    if (!plugins)
    {
        /* Empty list, this become the first entry. */
        plugins = plugin;
    }
    else
    {
        /* Non-empty list.  Add to end. */
        plugin_t *temp = plugins;

        while (temp->next)
            temp = temp->next;

        temp->next = plugin;
    }

    plugin_init_one(plugin);

    // check that context didn't change.  This should only happen if we
    // need a reentrant plugin_load.
    BX_ASSERT (current_plugin_context == plugin);
    current_plugin_context = NULL;

    return;
}

void
plugin_abort (void)
{
    pluginlog->panic("plugin load aborted");
}

#endif   /* end of #if BX_PLUGINS */

/************************************************************************/
/* Plugin system: initialisation of plugins entry points                */
/************************************************************************/

  void
plugin_startup(void)
{
  pluginRegisterIRQ = builtinRegisterIRQ;
  pluginUnregisterIRQ = builtinUnregisterIRQ;

  pluginSetHRQHackCallback = builtinSetHRQHackCallback;
  pluginSetHRQ = builtinSetHRQ;
  
  pluginRegisterIOReadHandler = builtinRegisterIOReadHandler;
  pluginRegisterIOWriteHandler = builtinRegisterIOWriteHandler;
  
  pluginUnregisterIOReadHandler = builtinUnregisterIOReadHandler;
  pluginUnregisterIOWriteHandler = builtinUnregisterIOWriteHandler;
  
  pluginRegisterIOReadHandlerRange = builtinRegisterIOReadHandlerRange;
  pluginRegisterIOWriteHandlerRange = builtinRegisterIOWriteHandlerRange;

  pluginUnregisterIOReadHandlerRange = builtinUnregisterIOReadHandlerRange;
  pluginUnregisterIOWriteHandlerRange = builtinUnregisterIOWriteHandlerRange;
  
  pluginRegisterDefaultIOReadHandler = builtinRegisterDefaultIOReadHandler;
  pluginRegisterDefaultIOWriteHandler = builtinRegisterDefaultIOWriteHandler;

  pluginRegisterTimer = builtinRegisterTimer;
  pluginActivateTimer = builtinActivateTimer;

#if BX_PLUGINS
  pluginlog = new logfunctions();
  pluginlog->put("PLGIN");
  pluginlog->settype(PLUGINLOG);
  int status = lt_dlinit ();
  if (status != 0) {
    BX_ERROR (("initialization error in ltdl library (for loading plugins)"));
    BX_PANIC (("error message was: %s", lt_dlerror ()));
  }
#endif
}


/************************************************************************/
/* Plugin system: Device registration                                   */
/************************************************************************/

void pluginRegisterDeviceDevmodel(plugin_t *plugin, plugintype_t type, bx_devmodel_c *devmodel, char *name)
{
    device_t *device;

    device = (device_t *)malloc (sizeof (device_t));
    if (!device)
    {
        pluginlog->panic("can't allocate device_t");
    }

    device->name = name;
    BX_ASSERT (devmodel != NULL);
    device->devmodel = devmodel;
    device->plugin = plugin;  // this can be NULL
    device->use_devmodel_interface = 1;
    device->device_init_mem = NULL;  // maybe should use 1 to detect any use?
    device->device_init_dev = NULL;
    device->device_reset = NULL;
    device->device_load_state = NULL;
    device->device_save_state = NULL;
    device->next = NULL;

    // Don't add every kind of device to the list.
    switch (type) {
      case PLUGTYPE_CORE:
        // Core devices are present whether or not we are using plugins, so
        // they are managed by the same code in iodev/devices.cc whether
        // plugins are on or off.  
        return; // Do not add core devices to the devices list.
      case PLUGTYPE_OPTIONAL:
      case PLUGTYPE_USER:
      default:
        // The plugin system will manage optional and user devices only.
        break;
    }

    if (!devices)
    {
        /* Empty list, this become the first entry. */
        devices = device;
    }
    else
    {
        /* Non-empty list.  Add to end. */
        device_t *temp = devices;

        while (temp->next)
            temp = temp->next;

        temp->next = device;
    }
}

/************************************************************************/
/* Plugin system: Check if a plugin is loaded                           */
/************************************************************************/

bx_bool pluginDevicePresent(char *name)
{
    device_t *device;

    for (device = devices; device; device = device->next)
    {
      if (strcmp(device->name,name)==0) return true;
    }

    return false;
}

#if BX_PLUGINS
/************************************************************************/
/* Plugin system: Load one plugin                                       */
/************************************************************************/

int bx_load_plugin (const char *name, plugintype_t type)
{
  char *namecopy = new char[1+strlen(name)];
  strcpy (namecopy, name);
  plugin_load (namecopy, "", type);
  return 0;
}
#endif   /* end of #if BX_PLUGINS */

/*************************************************************************/
/* Plugin system: Execute init function of all registered plugin-devices */
/*************************************************************************/

void bx_init_plugins()
{
    device_t *device;

    // two loops
    for (device = devices; device; device = device->next)
    {
      if (!device->use_devmodel_interface) {
        if (device->device_init_mem != NULL) {
            pluginlog->info("init_mem of '%s' plugin device by function pointer",device->name);
            device->device_init_mem(BX_MEM(0));
        }
      } else {
        pluginlog->info("init_mem of '%s' plugin device by virtual method",device->name);
        device->devmodel->init_mem (BX_MEM(0));
      }
    }

    for (device = devices; device; device = device->next)
    {
      if (!device->use_devmodel_interface) {
        if (device->device_init_dev != NULL) {
            pluginlog->info("init_dev of '%s' plugin device by function pointer",device->name);
            device->device_init_dev();
        }
      } else {
        pluginlog->info("init_dev of '%s' plugin device by virtual method",device->name);
        device->devmodel->init ();
      }
    } 
}

/**************************************************************************/
/* Plugin system: Execute reset function of all registered plugin-devices */
/**************************************************************************/

void bx_reset_plugins(unsigned signal)
{
    device_t *device;
    for (device = devices; device; device = device->next)
    {
      if (!device->use_devmodel_interface) {
        if (device->device_reset != NULL) {
            pluginlog->info("reset of '%s' plugin device by function pointer",device->name);
            device->device_reset(signal);
        }
      } else {
        pluginlog->info("reset of '%s' plugin device by virtual method",device->name);
        device->devmodel->reset (signal);
      }
    }
}

}
